/*
 * Alloy Analyzer
 * Copyright (c) 2002 Massachusetts Institute of Technology
 *
 * The Alloy Analyzer is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * The Alloy Analyzer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Alloy Analyzer; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package alloy.ast;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import alloy.util.Dbg;
import alloy.util.ObjID;

/**
 * An implementation of a generic AST node.
 * It stores source location in addition to providing
 * operations on the node's children.
 */

public abstract class TreeNode extends ObjID implements Node {//, HasCreationInfo {

    // debugging
    //    private CreationInfo _creationInfo = new CreationInfo();

    //    public CreationInfo getCreationInfo() {
    //	return _creationInfo;
    //}

    /**
     * children of this
     */
    private List _children; // List[Node] -- change to Collections.EMPTY_LIST
                            // until first child is added ...

    /** parent of this, may be null */
    private Node _parent;

    /**
     * location of this
     */
    private Location _location;

    /**
     * If this node resulted from inlining a function, the location of the
     * inlined function invocation; otherwise, <code>null</code>.
     */
    private List /* of Location */ _invocationLocations;

    /** The annotation of this -- typically used to explain the role of this node
    in the higher-level structure of the formula (e.g. "conjunction of all facts",
    "instantiation of function X", "declaration constraints from declaration of Y",
    "constraints from instantiating polymorphic fact Z", etc.)  */
    private String _annotation = "";

    /** Transform annotation -- typically used to explain the transformation that resulted
    in this node's creation, e.g. "desugaring of with".  */
    private String _transformAnnotation = "";

    /**
     * an extra description of the node, used
     * in generating {@link #fullString}
     */
    String descr = "";

    /**
     * if true, description of this has already been set
     */
    private boolean _descrSet;


    public TreeNode(Location location_) {
        _children = Collections.EMPTY_LIST;
        _location = location_;
    }

    public TreeNode() {
    		this(Location.UNKNOWN);
    }

    // convenience constructors for intially adding children

    public TreeNode(Location location_, Node n1_) {
        Dbg.chk(n1_ != null, "attempt to add null node 1 to AST");
        _location = location_;
        _children = new ArrayList(5);
        _children.add(n1_);
        n1_.setParent(this);
    }

    public TreeNode(Location location_, Node n1_, Node n2_) {
        this(location_, n1_);
        Dbg.chk(n2_ != null, "attempt to add null node 2 to AST");
        _children.add(n2_);
        n2_.setParent(this);
    }

    public TreeNode(Location location_, Node n1_, Node n2_, Node n3_) {
        this(location_, n1_, n2_);
        Dbg.chk(n3_ != null, "attempt to add null node 3 to AST");
        _children.add(n3_);
        n3_.setParent(this);
    }

    public TreeNode(Location location_, Node n1_, Node n2_, Node n3_, Node n4_) {
        this(location_, n1_, n2_, n3_);
        Dbg.chk(n4_ != null, "attempt to add null node 4 to AST");
        _children.add(n4_);
        n4_.setParent(this);
    }

    public TreeNode(Location location_, Node n1_, Node n2_, Node n3_, Node n4_, Node n5_) {
        this(location_, n1_, n2_, n3_, n4_);
        Dbg.chk(n5_ != null, "attempt to add null node 5 to AST");
        _children.add(n5_);
        n5_.setParent(this);
    }

    public TreeNode(Location location_, Node n1_, Node n2_, Node n3_, Node n4_, Node n5_, Node n6_) {
        this(location_, n1_, n2_, n3_, n4_, n5_);
        Dbg.chk(n6_ != null, "attempt to add null node 6 to AST");
        _children.add(n6_);
        n6_.setParent(this);
    }

    // INTERFACE

    /**
     * gets the source loction of this node
     * @return the {@link Location}
     */
    public final Location getLocation() {
        return _location;
    }

    /**
     * sets the source location of this node
     * @param location_ the new {@link Location} of this
     */
    public final void setLocation(Location location_) {
        _location = location_;
    }

    /**
     * Returns the {@link List} of {@link Location}s of nested function invocations
     * that led to placing this node's subtree into the AST.
     * These locations will be highlighted if the user clicks
     * on this node in the GUI tree representation of the AST.
     * @see alloy.gui.ASTTree#treeSelectionListener
     * @see alloy.gui.LocDisplayer
     */
    public final List /* of Location */ getInvocationLocations() {
        return _invocationLocations==null ? Collections.EMPTY_LIST : _invocationLocations;
    }

    /**
     * Adds an invocation location, to be highlighted when the user
     * clicks this node in the GUI tree representation of the AST.
     * The location indicates that this subtree of the AST appeared
     * as a result of function invocation at some place in the
     * undesugared AST.  Helps the user trace through the chain
     * of function invocations that led to this node's subtree
     * of the desugared AST.
     * Only done by {@link alloy.transform.InvocationInlineVisitor}
     * when inlining invocations.
     * @see alloy.gui.ASTTree#treeSelectionListener
     * @see alloy.gui.LocDisplayer
     */
    public final void addInvocationLocation(Location invocationLocation_) {
    if (_invocationLocations == null)
        _invocationLocations = new ArrayList();
    _invocationLocations.add(invocationLocation_);
    }

    /**
     * gets an iteration of the children of this
     * @return the Iterator
     */
    public final Iterator getChildren() {
        return _children.iterator();
    }

    /**
     * @return the parent of this, or <code>null</code>
     * if node has no parent
     */
    public final Node getParent() {
        return _parent;
    }


    /**
     * mutate this by changing its parent
     * @param parent_ new parent of this
     */
    public final void setParent(Node parent_) {
        _parent = parent_;
    }

    /**
     * gets the number of children of this
     * @return the number of children
     */
    public final int numChildren() {
        return _children.size();
    }

    /**
     * @param node_ a Node
     * @return true if node_ is a child of this
     */
    public final boolean hasChild(Node node_) {
        return _children.contains(node_);
    }

    /**
     * get the child at the specified index
     * @param index_ index of the desired child
     * @return node at desired index
     */
    public final Node childAt(int index_) {
        return (Node)_children.get(index_);
    }

    /**
     * return the index of the child
v     * @param child_ the child whose index is desired
     * @return the child's index; -1 if <code>child_</code> is not a child
     */
    public final int indexOf(Node child_) {
    return _children.indexOf(child_);
    }

    public void setChild(int index_, Node node_) {
        Dbg.chk(node_ != null, "attempt to set an AST node child to null " + nodeString());
    _children.set(index_, node_);
        node_.setParent(this);
    }

    /**
     * performs a deep copy of the node
     * should be overridden by subclasses which require
     * different behavior for a correct deep copy
     * @return a copy of the node, with the entire subtree also
     */
    public Object copy() {
        // holds the new copy of the node
        TreeNode nodeCopy = null;
        List clonedChildren = null;
        try {
            // make copies of the node object itself and the children list
            // using clone()
            // NOTE: for top-level call to copy, parent pointer
            // remains unchanged...not sure if this is right behavior
            nodeCopy = (TreeNode) clone();
            clonedChildren = (_children == Collections.EMPTY_LIST ? 
        						_children : new ArrayList(_children));
            nodeCopy._children = clonedChildren;
        } catch (CloneNotSupportedException e) {
            Dbg.fail("this is unreachable");
        }
        // now do the deep copy by copying all the children
    int size = clonedChildren.size();
        for (int i = 0; i < size; i++) {
            clonedChildren.set(i, ((Node)clonedChildren.get(i)).copy());
            // set parent pointer appropriately
            ((Node)clonedChildren.get(i)).setParent(nodeCopy);
        }

        return nodeCopy;
    }

    /** @return true if this is a leaf */
    public final boolean isLeaf() {
        return _children.size() == 0;
    }

   

    /**
     * get a textual representation of the original node
     */
    public String nodeString() {
        return 	childrenStr(""," ", "");
    }

    /** Returns the <em>not</em> fully-qualified class name (MyClass as opposed to alloy.ast.MyClass), for more
    compact display. */
    private String _getNodeClassName() {
        String fullName = getClass().getName();
        int lastDotPos = fullName.lastIndexOf('.');
        return lastDotPos >= 0 ? fullName.substring(lastDotPos + 1) : fullName;
    }

    public final String fullString() {
        if (_descrSet) {
            this.setDescr();
            _descrSet = true;
        }
        return _getNodeClassName() + ":" + hashCode()
            + ": "
            + nodeString()
            + (descr.equals("") ? "" : (", " + descr));
    }

    /** set the description of this, by default a no-op */
    void setDescr() {}

    public final String fullStringShortNames() {
        if (_descrSet) {
            this.setDescr();
            _descrSet = true;
        }
        return _getNodeClassName()
            + ": "
            + nodeString()
            + (descr.equals("") ? "" : (", " + descr));
    }

    public final Node annotate(String annotation_) {
        if (!annotation_.equals("")) {
            _annotation =
                _annotation.equals("") ? annotation_ : _annotation + "; " + annotation_;
        }
        return this;
    }

    public final Node annotateReplacing(String annotation_, Node other_) {
        setLocation(other_.getLocation());
        annotate(other_.getAnnotation());
        annotateTransform(other_.getTransformAnnotation());
        return annotate(annotation_);
    }

    public final String getAnnotation() {
        return _annotation;
    }

    /** Set the transform annotation of this node -- typically used to explain the transformation that resulted
    in this node's creation, e.g. "desugaring of with".  */
    public final Node annotateTransform(String transformAnnotation_) {
        if (!transformAnnotation_.equals("")) {
            _transformAnnotation =
                _transformAnnotation.equals("")
                    ? transformAnnotation_
                    : _transformAnnotation + "; " + transformAnnotation_;
        }
        return this;
    }

    public final Node annotateTransformReplacing(
        String transformAnnotation_,
        Node other_) {
        setLocation(other_.getLocation());
        annotate(other_.getAnnotation());
        annotateTransform(other_.getTransformAnnotation());
        return annotateTransform(transformAnnotation_);
    }

    /** @return the transform annotation of this node -- typically used to explain the transformation that resulted
    in this node's creation, e.g. "desugaring of with".  */
    public final String getTransformAnnotation() {
        return _transformAnnotation;
    }

    public void applyVisitor(ASTVisitor visitor_) {
        acceptVisitor(visitor_);
    }

    public Object applyReturnVisitor(ASTReturnVisitor visitor_) {
        return acceptReturnVisitor(visitor_);
    }

    // OTHER PUBLIC METHODS

    /**
     * generate a string representation of the children of this
     * generates child Strings using nodeString()
     * @param prefix_ String prefix for representation
     * @param separator_ String separator, goes between child Strings
     * @param postfix_ String postfix for representation
     * @param chooser_ chooses which children nodes will be added to the
     *                 String.  currently, either original nodes or replaced
     *                 nodes are chosen.
     * @return the {@link String}
     */
    public final String childrenStr(String prefix_, String separator_, String postfix_) {
        StringBuffer ret = new StringBuffer();
        boolean hasChildren = false;
    int size = _children.size();
        if (size > 0) {
            ret.append(prefix_);
            hasChildren = true;
        }
        for (int i = 0; i < size; i++) {
            // choose the child node using the chooser
            Node currentChild = (Node)_children.get(i);
            ret.append(currentChild.nodeString());
            // only add the separator if we're not to the last child
            if ((i+1) < size) {
                ret.append(separator_);
            }
        }
        if (hasChildren) {
            ret.append(postfix_);
        }
        return ret.toString();
    }

    /**
     * get text representation for a TreeNode
     * Currently, a textual representation of the original node.
     * This method should be overridden by classes which need a
     * different behavior.
     * @return the String
     */
    public String toString() {
        return nodeString();
    }

    // PACKAGE VISIBLE / PRIVATE

    /**
     * add a child to this
     *@param node_ new child
     */
    final void addChild(Node node_) {
        Dbg.chk(node_ != null, "attempt to add null node to AST g");
        if (_children==Collections.EMPTY_LIST)
        		_children = new ArrayList(5);
        _children.add(node_);
        node_.setParent(this);
    }

    /**
     * set the child at the specified index to be a new Node
     * @param index_ index of child to change
     * @param node_ new child
     */
    private void setChildAt(int index_, Node node_) {
        Dbg.chk(
            (index_ >= 0) && (index_ < _children.size()),
            "invalid index in call to setChildAt()");
        Dbg.chk(node_ != null, "attempt to add null node to AST h");
        _children.set(index_, node_);
        node_.setParent(this);
    }

    /**
     * accept an AST visitor
     * @param visitor_ the visitor
     */
    protected void acceptVisitor(ASTVisitor visitor_) {
        visitor_.visit(this);
    }

    /**
     * accept a returning AST visitor
     * @param visitor_ the visitor
     * @return the Object returned by the visitor
     */
    protected Object acceptReturnVisitor(ASTReturnVisitor visitor_) {
        return visitor_.visit(this);
    }

    /**
     * Test for structural equality: whether the given node is an identical
     * copy of this node.
     */
    public boolean isSameAs(Node node_) {
    if (node_==null || !node_.getClass().equals(getClass()) ||
        node_.numChildren()!=numChildren())
        return false;
    if (!(node_ instanceof Leaf)) {
        for (int childNum=0; childNum<numChildren(); childNum++) {
        if (!childAt(childNum).isSameAs(node_.childAt(childNum)))
            return false;
        }
    }
    return true;
    }

    /** @return a pretty-printed {@link String} representing this node's subtree */
    public String pp() {
        PrettyPrintVisitor ppv = new PrettyPrintVisitor();
        applyVisitor(ppv);
        return ppv.getPPrintString();
    }
}
